From: Benjamin Otte Date: Sun, 26 Mar 2023 00:01:29 +0000 (+0100) Subject: gtk-demo: Rename dropdown demo X-Git-Tag: archive/raspbian/4.12.3+ds-1+rpi1~1^2^2^2~59^2^2~501^2 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=5973df22aaada8164ebd4ef83c00d257731053d9;p=gtk4.git gtk-demo: Rename dropdown demo It's the listview selections demo, so use that name. --- diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 206833b185..615d782bfe 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -43,7 +43,7 @@ cssview.css reset.css - + suggestionentry.h suggestionentry.c suggestionentry.css @@ -274,7 +274,6 @@ cursors.c dialog.c drawingarea.c - dropdown.c dnd.c editable_cells.c entry_completion.c @@ -311,6 +310,7 @@ listview_clocks.c listview_filebrowser.c listview_minesweeper.c + listview_selections.c listview_settings.c listview_ucd.c listview_weather.c diff --git a/demos/gtk-demo/dropdown.c b/demos/gtk-demo/dropdown.c deleted file mode 100644 index a4d001f4f6..0000000000 --- a/demos/gtk-demo/dropdown.c +++ /dev/null @@ -1,571 +0,0 @@ -/* Lists/Selections - * - * The GtkDropDown widget is a modern alternative to GtkComboBox. - * It uses list models instead of tree models, and the content is - * displayed using widgets instead of cell renderers. - * - * This example also shows a custom widget that can replace - * GtkEntryCompletion or GtkComboBoxText. It is not currently - * part of GTK. - */ - -#include -#include "suggestionentry.h" - -#define STRING_TYPE_HOLDER (string_holder_get_type ()) -G_DECLARE_FINAL_TYPE (StringHolder, string_holder, STRING, HOLDER, GObject) - -struct _StringHolder { - GObject parent_instance; - char *title; - char *icon; - char *description; -}; - -G_DEFINE_TYPE (StringHolder, string_holder, G_TYPE_OBJECT); - -static void -string_holder_init (StringHolder *holder) -{ -} - -static void -string_holder_finalize (GObject *object) -{ - StringHolder *holder = STRING_HOLDER (object); - - g_free (holder->title); - g_free (holder->icon); - g_free (holder->description); - - G_OBJECT_CLASS (string_holder_parent_class)->finalize (object); -} - -static void -string_holder_class_init (StringHolderClass *class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (class); - - object_class->finalize = string_holder_finalize; -} - -static StringHolder * -string_holder_new (const char *title, const char *icon, const char *description) -{ - StringHolder *holder = g_object_new (STRING_TYPE_HOLDER, NULL); - holder->title = g_strdup (title); - holder->icon = g_strdup (icon); - holder->description = g_strdup (description); - return holder; -} - -static void -strings_setup_item_single_line (GtkSignalListItemFactory *factory, - GtkListItem *item) -{ - GtkWidget *box, *image, *title; - GtkWidget *checkmark; - - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); - - image = gtk_image_new (); - title = gtk_label_new (""); - gtk_label_set_xalign (GTK_LABEL (title), 0.0); - checkmark = gtk_image_new_from_icon_name ("object-select-symbolic"); - - gtk_box_append (GTK_BOX (box), image); - gtk_box_append (GTK_BOX (box), title); - gtk_box_append (GTK_BOX (box), checkmark); - - g_object_set_data (G_OBJECT (item), "title", title); - g_object_set_data (G_OBJECT (item), "image", image); - g_object_set_data (G_OBJECT (item), "checkmark", checkmark); - - gtk_list_item_set_child (item, box); -} - -static void -strings_setup_item_full (GtkSignalListItemFactory *factory, - GtkListItem *item) -{ - GtkWidget *box, *box2, *image, *title, *description; - GtkWidget *checkmark; - - image = gtk_image_new (); - title = gtk_label_new (""); - gtk_label_set_xalign (GTK_LABEL (title), 0.0); - description = gtk_label_new (""); - gtk_label_set_xalign (GTK_LABEL (description), 0.0); - gtk_widget_add_css_class (description, "dim-label"); - checkmark = gtk_image_new_from_icon_name ("object-select-symbolic"); - - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); - box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); - - gtk_box_append (GTK_BOX (box), image); - gtk_box_append (GTK_BOX (box), box2); - gtk_box_append (GTK_BOX (box2), title); - gtk_box_append (GTK_BOX (box2), description); - gtk_box_append (GTK_BOX (box), checkmark); - - g_object_set_data (G_OBJECT (item), "title", title); - g_object_set_data (G_OBJECT (item), "image", image); - g_object_set_data (G_OBJECT (item), "description", description); - g_object_set_data (G_OBJECT (item), "checkmark", checkmark); - - gtk_list_item_set_child (item, box); -} - -static void -selected_item_changed (GtkDropDown *dropdown, - GParamSpec *pspec, - GtkListItem *item) -{ - GtkWidget *checkmark; - - checkmark = g_object_get_data (G_OBJECT (item), "checkmark"); - - if (gtk_drop_down_get_selected_item (dropdown) == gtk_list_item_get_item (item)) - gtk_widget_set_opacity (checkmark, 1.0); - else - gtk_widget_set_opacity (checkmark, 0.0); -} - -static void -strings_bind_item (GtkSignalListItemFactory *factory, - GtkListItem *item, - gpointer data) -{ - GtkDropDown *dropdown = data; - GtkWidget *image, *title, *description; - GtkWidget *checkmark; - StringHolder *holder; - GtkWidget *popup; - - holder = gtk_list_item_get_item (item); - - title = g_object_get_data (G_OBJECT (item), "title"); - image = g_object_get_data (G_OBJECT (item), "image"); - description = g_object_get_data (G_OBJECT (item), "description"); - checkmark = g_object_get_data (G_OBJECT (item), "checkmark"); - - gtk_label_set_label (GTK_LABEL (title), holder->title); - if (image) - { - gtk_image_set_from_icon_name (GTK_IMAGE (image), holder->icon); - gtk_widget_set_visible (image, holder->icon != NULL); - } - if (description) - { - gtk_label_set_label (GTK_LABEL (description), holder->description); - gtk_widget_set_visible (description , holder->description != NULL); - } - - popup = gtk_widget_get_ancestor (title, GTK_TYPE_POPOVER); - if (popup && gtk_widget_is_ancestor (popup, GTK_WIDGET (dropdown))) - { - gtk_widget_set_visible (checkmark, TRUE); - g_signal_connect (dropdown, "notify::selected-item", - G_CALLBACK (selected_item_changed), item); - selected_item_changed (dropdown, NULL, item); - } - else - { - gtk_widget_set_visible (checkmark, FALSE); - } -} - -static void -strings_unbind_item (GtkSignalListItemFactory *factory, - GtkListItem *list_item, - gpointer data) -{ - GtkDropDown *dropdown = data; - - g_signal_handlers_disconnect_by_func (dropdown, selected_item_changed, list_item); -} - -static GtkListItemFactory * -strings_factory_new (gpointer data, gboolean full) -{ - GtkListItemFactory *factory; - - factory = gtk_signal_list_item_factory_new (); - if (full) - g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_full), data); - else - g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_single_line), data); - g_signal_connect (factory, "bind", G_CALLBACK (strings_bind_item), data); - g_signal_connect (factory, "unbind", G_CALLBACK (strings_unbind_item), data); - - return factory; -} - -static GListModel * -strings_model_new (const char *const *titles, - const char *const *icons, - const char *const *descriptions) -{ - GListStore *store; - int i; - - store = g_list_store_new (STRING_TYPE_HOLDER); - for (i = 0; titles[i]; i++) - { - StringHolder *holder = string_holder_new (titles[i], - icons ? icons[i] : NULL, - descriptions ? descriptions[i] : NULL); - g_list_store_append (store, holder); - g_object_unref (holder); - } - - return G_LIST_MODEL (store); -} - -static GtkWidget * -drop_down_new_from_strings (const char *const *titles, - const char *const *icons, - const char *const *descriptions) -{ - GtkWidget *widget; - GListModel *model; - GtkListItemFactory *factory; - GtkListItemFactory *list_factory; - - g_return_val_if_fail (titles != NULL, NULL); - g_return_val_if_fail (icons == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)titles), NULL); - g_return_val_if_fail (descriptions == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)descriptions), NULL); - - model = strings_model_new (titles, icons, descriptions); - widget = g_object_new (GTK_TYPE_DROP_DOWN, - "model", model, - NULL); - g_object_unref (model); - - factory = strings_factory_new (widget, FALSE); - if (icons != NULL || descriptions != NULL) - list_factory = strings_factory_new (widget, TRUE); - else - list_factory = NULL; - - g_object_set (widget, - "factory", factory, - "list-factory", list_factory, - NULL); - - g_object_unref (factory); - if (list_factory) - g_object_unref (list_factory); - - return widget; -} - -static char * -get_family_name (gpointer item) -{ - return g_strdup (pango_font_family_get_name (PANGO_FONT_FAMILY (item))); -} - -static char * -get_title (gpointer item) -{ - return g_strdup (STRING_HOLDER (item)->title); -} - -static char * -get_file_name (gpointer item) -{ - return g_strdup (g_file_info_get_display_name (G_FILE_INFO (item))); -} - -static void -setup_item (GtkSignalListItemFactory *factory, - GtkListItem *item) -{ - GtkWidget *box; - GtkWidget *icon; - GtkWidget *label; - - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); - icon = gtk_image_new (); - label = gtk_label_new (""); - gtk_label_set_xalign (GTK_LABEL (label), 0); - gtk_box_append (GTK_BOX (box), icon); - gtk_box_append (GTK_BOX (box), label); - gtk_list_item_set_child (item, box); -} - -static void -bind_item (GtkSignalListItemFactory *factory, - GtkListItem *item) -{ - MatchObject *match = MATCH_OBJECT (gtk_list_item_get_item (item)); - GFileInfo *info = G_FILE_INFO (match_object_get_item (match)); - GtkWidget *box = gtk_list_item_get_child (item); - GtkWidget *icon = gtk_widget_get_first_child (box); - GtkWidget *label = gtk_widget_get_last_child (box); - - gtk_image_set_from_gicon (GTK_IMAGE (icon), g_file_info_get_icon (info)); - gtk_label_set_label (GTK_LABEL (label), g_file_info_get_display_name (info)); -} - -static void -setup_highlight_item (GtkSignalListItemFactory *factory, - GtkListItem *item) -{ - GtkWidget *label; - - label = gtk_label_new (""); - gtk_label_set_xalign (GTK_LABEL (label), 0); - gtk_list_item_set_child (item, label); -} - -static void -bind_highlight_item (GtkSignalListItemFactory *factory, - GtkListItem *item) -{ - MatchObject *obj; - GtkWidget *label; - PangoAttrList *attrs; - PangoAttribute *attr; - const char *str; - - obj = MATCH_OBJECT (gtk_list_item_get_item (item)); - label = gtk_list_item_get_child (item); - - str = match_object_get_string (obj); - - gtk_label_set_label (GTK_LABEL (label), str); - attrs = pango_attr_list_new (); - attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); - attr->start_index = match_object_get_match_start (obj); - attr->end_index = match_object_get_match_end (obj); - pango_attr_list_insert (attrs, attr); - gtk_label_set_attributes (GTK_LABEL (label), attrs); - pango_attr_list_unref (attrs); -} - -static void -match_func (MatchObject *obj, - const char *search, - gpointer user_data) -{ - char *tmp1, *tmp2; - char *p; - - tmp1 = g_utf8_normalize (match_object_get_string (obj), -1, G_NORMALIZE_ALL); - tmp2 = g_utf8_normalize (search, -1, G_NORMALIZE_ALL); - - if ((p = strstr (tmp1, tmp2)) != NULL) - match_object_set_match (obj, - p - tmp1, - (p - tmp1) + g_utf8_strlen (search, -1), - 1); - else - match_object_set_match (obj, 0, 0, 0); - - g_free (tmp1); - g_free (tmp2); -} - -GtkWidget * -do_dropdown (GtkWidget *do_widget) -{ - static GtkWidget *window = NULL; - GtkWidget *button, *box, *spin, *check, *hbox, *label, *entry; - GListModel *model; - GtkExpression *expression; - GtkListItemFactory *factory; - const char * const times[] = { "1 minute", "2 minutes", "5 minutes", "20 minutes", NULL }; - const char * const many_times[] = { - "1 minute", "2 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", - "25 minutes", "30 minutes", "35 minutes", "40 minutes", "45 minutes", "50 minutes", - "55 minutes", "1 hour", "2 hours", "3 hours", "5 hours", "6 hours", "7 hours", - "8 hours", "9 hours", "10 hours", "11 hours", "12 hours", NULL - }; - const char * const device_titles[] = { "Digital Output", "Headphones", "Digital Output", "Analog Output", NULL }; - const char * const device_icons[] = { "audio-card-symbolic", "audio-headphones-symbolic", "audio-card-symbolic", "audio-card-symbolic", NULL }; - const char * const device_descriptions[] = { - "Built-in Audio", "Built-in audio", "Thinkpad Tunderbolt 3 Dock USB Audio", "Thinkpad Tunderbolt 3 Dock USB Audio", NULL - }; - char *cwd; - GFile *file; - GListModel *dir; - GtkStringList *strings; - - if (!window) - { - window = gtk_window_new (); - gtk_window_set_display (GTK_WINDOW (window), - gtk_widget_get_display (do_widget)); - gtk_window_set_title (GTK_WINDOW (window), "Selections"); - gtk_window_set_resizable (GTK_WINDOW (window), FALSE); - g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window); - - hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 20); - - gtk_widget_set_margin_start (hbox, 20); - gtk_widget_set_margin_end (hbox, 20); - gtk_widget_set_margin_top (hbox, 20); - gtk_widget_set_margin_bottom (hbox, 20); - gtk_window_set_child (GTK_WINDOW (window), hbox); - - box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); - gtk_box_append (GTK_BOX (hbox), box); - - label = gtk_label_new ("Dropdowns"); - gtk_widget_add_css_class (label, "title-4"); - gtk_box_append (GTK_BOX (box), label); - - /* A basic dropdown */ - button = drop_down_new_from_strings (times, NULL, NULL); - gtk_box_append (GTK_BOX (box), button); - - /* A dropdown using an expression to obtain strings */ - button = drop_down_new_from_strings (many_times, NULL, NULL); - gtk_drop_down_set_enable_search (GTK_DROP_DOWN (button), TRUE); - expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, - 0, NULL, - (GCallback)get_title, - NULL, NULL); - gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression); - gtk_expression_unref (expression); - gtk_box_append (GTK_BOX (box), button); - - button = gtk_drop_down_new (NULL, NULL); - - model = G_LIST_MODEL (pango_cairo_font_map_get_default ()); - gtk_drop_down_set_model (GTK_DROP_DOWN (button), model); - gtk_drop_down_set_selected (GTK_DROP_DOWN (button), 0); - - expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, - 0, NULL, - (GCallback)get_family_name, - NULL, NULL); - gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression); - gtk_expression_unref (expression); - gtk_box_append (GTK_BOX (box), button); - - spin = gtk_spin_button_new_with_range (-1, g_list_model_get_n_items (G_LIST_MODEL (model)), 1); - gtk_widget_set_halign (spin, GTK_ALIGN_START); - gtk_widget_set_margin_start (spin, 20); - g_object_bind_property (button, "selected", spin, "value", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); - gtk_box_append (GTK_BOX (box), spin); - - check = gtk_check_button_new_with_label ("Enable search"); - gtk_widget_set_margin_start (check, 20); - g_object_bind_property (button, "enable-search", check, "active", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); - gtk_box_append (GTK_BOX (box), check); - - g_object_unref (model); - - /* A dropdown with a separate list factory */ - button = drop_down_new_from_strings (device_titles, device_icons, device_descriptions); - gtk_box_append (GTK_BOX (box), button); - - gtk_box_append (GTK_BOX (hbox), gtk_separator_new (GTK_ORIENTATION_VERTICAL)); - - box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); - gtk_box_append (GTK_BOX (hbox), box); - - label = gtk_label_new ("Suggestions"); - gtk_widget_add_css_class (label, "title-4"); - gtk_box_append (GTK_BOX (box), label); - - /* A basic suggestion entry */ - entry = suggestion_entry_new (); - g_object_set (entry, "placeholder-text", "Words with T or G…", NULL); - strings = gtk_string_list_new ((const char *[]){ - "GNOME", - "gnominious", - "Gnomonic projection", - "total", - "totally", - "toto", - "tottery", - "totterer", - "Totten trust", - "totipotent", - "totipotency", - "totemism", - "totem pole", - "Totara", - "totalizer", - "totalizator", - "totalitarianism", - "total parenteral nutrition", - "total hysterectomy", - "total eclipse", - "Totipresence", - "Totipalmi", - "Tomboy", - "zombie", - NULL}); - suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings)); - g_object_unref (strings); - - gtk_box_append (GTK_BOX (box), entry); - - /* A suggestion entry using a custom model, and no filtering */ - entry = suggestion_entry_new (); - - cwd = g_get_current_dir (); - file = g_file_new_for_path (cwd); - dir = G_LIST_MODEL (gtk_directory_list_new ("standard::display-name,standard::content-type,standard::icon,standard::size", file)); - suggestion_entry_set_model (SUGGESTION_ENTRY (entry), dir); - g_object_unref (dir); - g_object_unref (file); - g_free (cwd); - - expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, - 0, NULL, - (GCallback)get_file_name, - NULL, NULL); - suggestion_entry_set_expression (SUGGESTION_ENTRY (entry), expression); - gtk_expression_unref (expression); - - factory = gtk_signal_list_item_factory_new (); - g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL); - g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL); - - suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory); - g_object_unref (factory); - - suggestion_entry_set_use_filter (SUGGESTION_ENTRY (entry), FALSE); - suggestion_entry_set_show_arrow (SUGGESTION_ENTRY (entry), TRUE); - - gtk_box_append (GTK_BOX (box), entry); - - /* A suggestion entry with match highlighting */ - entry = suggestion_entry_new (); - g_object_set (entry, "placeholder-text", "Destination", NULL); - - strings = gtk_string_list_new ((const char *[]){ - "app-mockups", - "settings-mockups", - "os-mockups", - "software-mockups", - "mocktails", - NULL}); - suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings)); - g_object_unref (strings); - - gtk_box_append (GTK_BOX (box), entry); - - suggestion_entry_set_match_func (SUGGESTION_ENTRY (entry), match_func, NULL, NULL); - - factory = gtk_signal_list_item_factory_new (); - g_signal_connect (factory, "setup", G_CALLBACK (setup_highlight_item), NULL); - g_signal_connect (factory, "bind", G_CALLBACK (bind_highlight_item), NULL); - suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory); - g_object_unref (factory); - - } - - if (!gtk_widget_get_visible (window)) - gtk_widget_set_visible (window, TRUE); - else - gtk_window_destroy (GTK_WINDOW (window)); - - return window; -} diff --git a/demos/gtk-demo/listview_selections.c b/demos/gtk-demo/listview_selections.c new file mode 100644 index 0000000000..fbca3b4e7d --- /dev/null +++ b/demos/gtk-demo/listview_selections.c @@ -0,0 +1,571 @@ +/* Lists/Selections + * + * The GtkDropDown widget is a modern alternative to GtkComboBox. + * It uses list models instead of tree models, and the content is + * displayed using widgets instead of cell renderers. + * + * This example also shows a custom widget that can replace + * GtkEntryCompletion or GtkComboBoxText. It is not currently + * part of GTK. + */ + +#include +#include "suggestionentry.h" + +#define STRING_TYPE_HOLDER (string_holder_get_type ()) +G_DECLARE_FINAL_TYPE (StringHolder, string_holder, STRING, HOLDER, GObject) + +struct _StringHolder { + GObject parent_instance; + char *title; + char *icon; + char *description; +}; + +G_DEFINE_TYPE (StringHolder, string_holder, G_TYPE_OBJECT); + +static void +string_holder_init (StringHolder *holder) +{ +} + +static void +string_holder_finalize (GObject *object) +{ + StringHolder *holder = STRING_HOLDER (object); + + g_free (holder->title); + g_free (holder->icon); + g_free (holder->description); + + G_OBJECT_CLASS (string_holder_parent_class)->finalize (object); +} + +static void +string_holder_class_init (StringHolderClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = string_holder_finalize; +} + +static StringHolder * +string_holder_new (const char *title, const char *icon, const char *description) +{ + StringHolder *holder = g_object_new (STRING_TYPE_HOLDER, NULL); + holder->title = g_strdup (title); + holder->icon = g_strdup (icon); + holder->description = g_strdup (description); + return holder; +} + +static void +strings_setup_item_single_line (GtkSignalListItemFactory *factory, + GtkListItem *item) +{ + GtkWidget *box, *image, *title; + GtkWidget *checkmark; + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + + image = gtk_image_new (); + title = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (title), 0.0); + checkmark = gtk_image_new_from_icon_name ("object-select-symbolic"); + + gtk_box_append (GTK_BOX (box), image); + gtk_box_append (GTK_BOX (box), title); + gtk_box_append (GTK_BOX (box), checkmark); + + g_object_set_data (G_OBJECT (item), "title", title); + g_object_set_data (G_OBJECT (item), "image", image); + g_object_set_data (G_OBJECT (item), "checkmark", checkmark); + + gtk_list_item_set_child (item, box); +} + +static void +strings_setup_item_full (GtkSignalListItemFactory *factory, + GtkListItem *item) +{ + GtkWidget *box, *box2, *image, *title, *description; + GtkWidget *checkmark; + + image = gtk_image_new (); + title = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (title), 0.0); + description = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (description), 0.0); + gtk_widget_add_css_class (description, "dim-label"); + checkmark = gtk_image_new_from_icon_name ("object-select-symbolic"); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + + gtk_box_append (GTK_BOX (box), image); + gtk_box_append (GTK_BOX (box), box2); + gtk_box_append (GTK_BOX (box2), title); + gtk_box_append (GTK_BOX (box2), description); + gtk_box_append (GTK_BOX (box), checkmark); + + g_object_set_data (G_OBJECT (item), "title", title); + g_object_set_data (G_OBJECT (item), "image", image); + g_object_set_data (G_OBJECT (item), "description", description); + g_object_set_data (G_OBJECT (item), "checkmark", checkmark); + + gtk_list_item_set_child (item, box); +} + +static void +selected_item_changed (GtkDropDown *dropdown, + GParamSpec *pspec, + GtkListItem *item) +{ + GtkWidget *checkmark; + + checkmark = g_object_get_data (G_OBJECT (item), "checkmark"); + + if (gtk_drop_down_get_selected_item (dropdown) == gtk_list_item_get_item (item)) + gtk_widget_set_opacity (checkmark, 1.0); + else + gtk_widget_set_opacity (checkmark, 0.0); +} + +static void +strings_bind_item (GtkSignalListItemFactory *factory, + GtkListItem *item, + gpointer data) +{ + GtkDropDown *dropdown = data; + GtkWidget *image, *title, *description; + GtkWidget *checkmark; + StringHolder *holder; + GtkWidget *popup; + + holder = gtk_list_item_get_item (item); + + title = g_object_get_data (G_OBJECT (item), "title"); + image = g_object_get_data (G_OBJECT (item), "image"); + description = g_object_get_data (G_OBJECT (item), "description"); + checkmark = g_object_get_data (G_OBJECT (item), "checkmark"); + + gtk_label_set_label (GTK_LABEL (title), holder->title); + if (image) + { + gtk_image_set_from_icon_name (GTK_IMAGE (image), holder->icon); + gtk_widget_set_visible (image, holder->icon != NULL); + } + if (description) + { + gtk_label_set_label (GTK_LABEL (description), holder->description); + gtk_widget_set_visible (description , holder->description != NULL); + } + + popup = gtk_widget_get_ancestor (title, GTK_TYPE_POPOVER); + if (popup && gtk_widget_is_ancestor (popup, GTK_WIDGET (dropdown))) + { + gtk_widget_set_visible (checkmark, TRUE); + g_signal_connect (dropdown, "notify::selected-item", + G_CALLBACK (selected_item_changed), item); + selected_item_changed (dropdown, NULL, item); + } + else + { + gtk_widget_set_visible (checkmark, FALSE); + } +} + +static void +strings_unbind_item (GtkSignalListItemFactory *factory, + GtkListItem *list_item, + gpointer data) +{ + GtkDropDown *dropdown = data; + + g_signal_handlers_disconnect_by_func (dropdown, selected_item_changed, list_item); +} + +static GtkListItemFactory * +strings_factory_new (gpointer data, gboolean full) +{ + GtkListItemFactory *factory; + + factory = gtk_signal_list_item_factory_new (); + if (full) + g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_full), data); + else + g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_single_line), data); + g_signal_connect (factory, "bind", G_CALLBACK (strings_bind_item), data); + g_signal_connect (factory, "unbind", G_CALLBACK (strings_unbind_item), data); + + return factory; +} + +static GListModel * +strings_model_new (const char *const *titles, + const char *const *icons, + const char *const *descriptions) +{ + GListStore *store; + int i; + + store = g_list_store_new (STRING_TYPE_HOLDER); + for (i = 0; titles[i]; i++) + { + StringHolder *holder = string_holder_new (titles[i], + icons ? icons[i] : NULL, + descriptions ? descriptions[i] : NULL); + g_list_store_append (store, holder); + g_object_unref (holder); + } + + return G_LIST_MODEL (store); +} + +static GtkWidget * +drop_down_new_from_strings (const char *const *titles, + const char *const *icons, + const char *const *descriptions) +{ + GtkWidget *widget; + GListModel *model; + GtkListItemFactory *factory; + GtkListItemFactory *list_factory; + + g_return_val_if_fail (titles != NULL, NULL); + g_return_val_if_fail (icons == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)titles), NULL); + g_return_val_if_fail (descriptions == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)descriptions), NULL); + + model = strings_model_new (titles, icons, descriptions); + widget = g_object_new (GTK_TYPE_DROP_DOWN, + "model", model, + NULL); + g_object_unref (model); + + factory = strings_factory_new (widget, FALSE); + if (icons != NULL || descriptions != NULL) + list_factory = strings_factory_new (widget, TRUE); + else + list_factory = NULL; + + g_object_set (widget, + "factory", factory, + "list-factory", list_factory, + NULL); + + g_object_unref (factory); + if (list_factory) + g_object_unref (list_factory); + + return widget; +} + +static char * +get_family_name (gpointer item) +{ + return g_strdup (pango_font_family_get_name (PANGO_FONT_FAMILY (item))); +} + +static char * +get_title (gpointer item) +{ + return g_strdup (STRING_HOLDER (item)->title); +} + +static char * +get_file_name (gpointer item) +{ + return g_strdup (g_file_info_get_display_name (G_FILE_INFO (item))); +} + +static void +setup_item (GtkSignalListItemFactory *factory, + GtkListItem *item) +{ + GtkWidget *box; + GtkWidget *icon; + GtkWidget *label; + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + icon = gtk_image_new (); + label = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_box_append (GTK_BOX (box), icon); + gtk_box_append (GTK_BOX (box), label); + gtk_list_item_set_child (item, box); +} + +static void +bind_item (GtkSignalListItemFactory *factory, + GtkListItem *item) +{ + MatchObject *match = MATCH_OBJECT (gtk_list_item_get_item (item)); + GFileInfo *info = G_FILE_INFO (match_object_get_item (match)); + GtkWidget *box = gtk_list_item_get_child (item); + GtkWidget *icon = gtk_widget_get_first_child (box); + GtkWidget *label = gtk_widget_get_last_child (box); + + gtk_image_set_from_gicon (GTK_IMAGE (icon), g_file_info_get_icon (info)); + gtk_label_set_label (GTK_LABEL (label), g_file_info_get_display_name (info)); +} + +static void +setup_highlight_item (GtkSignalListItemFactory *factory, + GtkListItem *item) +{ + GtkWidget *label; + + label = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_list_item_set_child (item, label); +} + +static void +bind_highlight_item (GtkSignalListItemFactory *factory, + GtkListItem *item) +{ + MatchObject *obj; + GtkWidget *label; + PangoAttrList *attrs; + PangoAttribute *attr; + const char *str; + + obj = MATCH_OBJECT (gtk_list_item_get_item (item)); + label = gtk_list_item_get_child (item); + + str = match_object_get_string (obj); + + gtk_label_set_label (GTK_LABEL (label), str); + attrs = pango_attr_list_new (); + attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); + attr->start_index = match_object_get_match_start (obj); + attr->end_index = match_object_get_match_end (obj); + pango_attr_list_insert (attrs, attr); + gtk_label_set_attributes (GTK_LABEL (label), attrs); + pango_attr_list_unref (attrs); +} + +static void +match_func (MatchObject *obj, + const char *search, + gpointer user_data) +{ + char *tmp1, *tmp2; + char *p; + + tmp1 = g_utf8_normalize (match_object_get_string (obj), -1, G_NORMALIZE_ALL); + tmp2 = g_utf8_normalize (search, -1, G_NORMALIZE_ALL); + + if ((p = strstr (tmp1, tmp2)) != NULL) + match_object_set_match (obj, + p - tmp1, + (p - tmp1) + g_utf8_strlen (search, -1), + 1); + else + match_object_set_match (obj, 0, 0, 0); + + g_free (tmp1); + g_free (tmp2); +} + +GtkWidget * +do_listview_selections (GtkWidget *do_widget) +{ + static GtkWidget *window = NULL; + GtkWidget *button, *box, *spin, *check, *hbox, *label, *entry; + GListModel *model; + GtkExpression *expression; + GtkListItemFactory *factory; + const char * const times[] = { "1 minute", "2 minutes", "5 minutes", "20 minutes", NULL }; + const char * const many_times[] = { + "1 minute", "2 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", + "25 minutes", "30 minutes", "35 minutes", "40 minutes", "45 minutes", "50 minutes", + "55 minutes", "1 hour", "2 hours", "3 hours", "5 hours", "6 hours", "7 hours", + "8 hours", "9 hours", "10 hours", "11 hours", "12 hours", NULL + }; + const char * const device_titles[] = { "Digital Output", "Headphones", "Digital Output", "Analog Output", NULL }; + const char * const device_icons[] = { "audio-card-symbolic", "audio-headphones-symbolic", "audio-card-symbolic", "audio-card-symbolic", NULL }; + const char * const device_descriptions[] = { + "Built-in Audio", "Built-in audio", "Thinkpad Tunderbolt 3 Dock USB Audio", "Thinkpad Tunderbolt 3 Dock USB Audio", NULL + }; + char *cwd; + GFile *file; + GListModel *dir; + GtkStringList *strings; + + if (!window) + { + window = gtk_window_new (); + gtk_window_set_display (GTK_WINDOW (window), + gtk_widget_get_display (do_widget)); + gtk_window_set_title (GTK_WINDOW (window), "Selections"); + gtk_window_set_resizable (GTK_WINDOW (window), FALSE); + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 20); + + gtk_widget_set_margin_start (hbox, 20); + gtk_widget_set_margin_end (hbox, 20); + gtk_widget_set_margin_top (hbox, 20); + gtk_widget_set_margin_bottom (hbox, 20); + gtk_window_set_child (GTK_WINDOW (window), hbox); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + gtk_box_append (GTK_BOX (hbox), box); + + label = gtk_label_new ("Dropdowns"); + gtk_widget_add_css_class (label, "title-4"); + gtk_box_append (GTK_BOX (box), label); + + /* A basic dropdown */ + button = drop_down_new_from_strings (times, NULL, NULL); + gtk_box_append (GTK_BOX (box), button); + + /* A dropdown using an expression to obtain strings */ + button = drop_down_new_from_strings (many_times, NULL, NULL); + gtk_drop_down_set_enable_search (GTK_DROP_DOWN (button), TRUE); + expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, + 0, NULL, + (GCallback)get_title, + NULL, NULL); + gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression); + gtk_expression_unref (expression); + gtk_box_append (GTK_BOX (box), button); + + button = gtk_drop_down_new (NULL, NULL); + + model = G_LIST_MODEL (pango_cairo_font_map_get_default ()); + gtk_drop_down_set_model (GTK_DROP_DOWN (button), model); + gtk_drop_down_set_selected (GTK_DROP_DOWN (button), 0); + + expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, + 0, NULL, + (GCallback)get_family_name, + NULL, NULL); + gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression); + gtk_expression_unref (expression); + gtk_box_append (GTK_BOX (box), button); + + spin = gtk_spin_button_new_with_range (-1, g_list_model_get_n_items (G_LIST_MODEL (model)), 1); + gtk_widget_set_halign (spin, GTK_ALIGN_START); + gtk_widget_set_margin_start (spin, 20); + g_object_bind_property (button, "selected", spin, "value", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + gtk_box_append (GTK_BOX (box), spin); + + check = gtk_check_button_new_with_label ("Enable search"); + gtk_widget_set_margin_start (check, 20); + g_object_bind_property (button, "enable-search", check, "active", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); + gtk_box_append (GTK_BOX (box), check); + + g_object_unref (model); + + /* A dropdown with a separate list factory */ + button = drop_down_new_from_strings (device_titles, device_icons, device_descriptions); + gtk_box_append (GTK_BOX (box), button); + + gtk_box_append (GTK_BOX (hbox), gtk_separator_new (GTK_ORIENTATION_VERTICAL)); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); + gtk_box_append (GTK_BOX (hbox), box); + + label = gtk_label_new ("Suggestions"); + gtk_widget_add_css_class (label, "title-4"); + gtk_box_append (GTK_BOX (box), label); + + /* A basic suggestion entry */ + entry = suggestion_entry_new (); + g_object_set (entry, "placeholder-text", "Words with T or G…", NULL); + strings = gtk_string_list_new ((const char *[]){ + "GNOME", + "gnominious", + "Gnomonic projection", + "total", + "totally", + "toto", + "tottery", + "totterer", + "Totten trust", + "totipotent", + "totipotency", + "totemism", + "totem pole", + "Totara", + "totalizer", + "totalizator", + "totalitarianism", + "total parenteral nutrition", + "total hysterectomy", + "total eclipse", + "Totipresence", + "Totipalmi", + "Tomboy", + "zombie", + NULL}); + suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings)); + g_object_unref (strings); + + gtk_box_append (GTK_BOX (box), entry); + + /* A suggestion entry using a custom model, and no filtering */ + entry = suggestion_entry_new (); + + cwd = g_get_current_dir (); + file = g_file_new_for_path (cwd); + dir = G_LIST_MODEL (gtk_directory_list_new ("standard::display-name,standard::content-type,standard::icon,standard::size", file)); + suggestion_entry_set_model (SUGGESTION_ENTRY (entry), dir); + g_object_unref (dir); + g_object_unref (file); + g_free (cwd); + + expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL, + 0, NULL, + (GCallback)get_file_name, + NULL, NULL); + suggestion_entry_set_expression (SUGGESTION_ENTRY (entry), expression); + gtk_expression_unref (expression); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL); + + suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory); + g_object_unref (factory); + + suggestion_entry_set_use_filter (SUGGESTION_ENTRY (entry), FALSE); + suggestion_entry_set_show_arrow (SUGGESTION_ENTRY (entry), TRUE); + + gtk_box_append (GTK_BOX (box), entry); + + /* A suggestion entry with match highlighting */ + entry = suggestion_entry_new (); + g_object_set (entry, "placeholder-text", "Destination", NULL); + + strings = gtk_string_list_new ((const char *[]){ + "app-mockups", + "settings-mockups", + "os-mockups", + "software-mockups", + "mocktails", + NULL}); + suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings)); + g_object_unref (strings); + + gtk_box_append (GTK_BOX (box), entry); + + suggestion_entry_set_match_func (SUGGESTION_ENTRY (entry), match_func, NULL, NULL); + + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_highlight_item), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (bind_highlight_item), NULL); + suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory); + g_object_unref (factory); + + } + + if (!gtk_widget_get_visible (window)) + gtk_widget_set_visible (window, TRUE); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; +} diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index 8cd428a969..c03beb3940 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -55,7 +55,7 @@ demos = files([ 'listview_colors.c', 'listview_filebrowser.c', 'listview_minesweeper.c', - 'dropdown.c', + 'listview_selections.c', 'listview_settings.c', 'listview_ucd.c', 'listview_weather.c', diff --git a/demos/gtk-demo/suggestionentry.c b/demos/gtk-demo/suggestionentry.c index 8a0b3aa082..f8a28c8375 100644 --- a/demos/gtk-demo/suggestionentry.c +++ b/demos/gtk-demo/suggestionentry.c @@ -849,7 +849,7 @@ suggestion_entry_init (SuggestionEntry *self) GtkCssProvider *provider; provider = gtk_css_provider_new (); - gtk_css_provider_load_from_resource (provider, "/dropdown/suggestionentry.css"); + gtk_css_provider_load_from_resource (provider, "/listview_selections/suggestionentry.css"); gtk_style_context_add_provider_for_display (gdk_display_get_default (), GTK_STYLE_PROVIDER (provider), 800);